﻿using System;
using System.Linq;
using System.Text;
using System.Threading;
using log4net;

namespace Sherwood.Content.Tasks
{
    public class TaskExecutionAgent
    {
        private static readonly ILog Log = LogManager.GetLogger(typeof(TaskExecutionAgent));

        private readonly Thread _thread;
        private readonly ManualResetEvent _checkStateHandle = new ManualResetEvent(false);
        private DateTime _nextTaskExecution = DateTime.MaxValue;
        private readonly IDataSessionProvider _dataSessionProvider;

        public ITaskManager TaskManager { get; private set; }
        public bool IsRunning { get; private set; }

        public TaskExecutionAgent(ITaskManager taskManager, IDataSessionProvider dataSessionProvider)
        {
            TaskManager = taskManager;
            TaskManager.TasksCreated += TasksManagerOnTasksCreated;

            _thread = new Thread(OnStart) { IsBackground = true, Name = "TaskExecutionAgent" };
            _dataSessionProvider = dataSessionProvider;
        }

        public void Start()
        {
            if (!IsRunning)
                _thread.Start();
        }      

        public void Stop()
        {
            if (IsRunning)
            {
                IsBeingStopped = true;
                _checkStateHandle.Set();
            }
        }

        public bool IsBeingStopped { get; private set; }

        private void TasksManagerOnTasksCreated(object sender, TasksCreatedEventArgs tasksCreatedEventArgs)
        {
            var nextTaskExecution = tasksCreatedEventArgs.Tasks.Min(t => t.PlannedDate.GetValueOrDefault(_nextTaskExecution));
            if (nextTaskExecution < _nextTaskExecution)
            {
                _nextTaskExecution = nextTaskExecution;
                _checkStateHandle.Set();
            }
        }

        private void OnStart()
        {
            Log.Info("Task execution agent started");
            IsRunning = true;
            try
            {
                using (_dataSessionProvider.CreateSession())
                {
                    _nextTaskExecution = TaskManager.GetNextTaskExecution();
                    while (!IsBeingStopped)
                    {
                        _checkStateHandle.Reset();

                        var timeToWait = _nextTaskExecution.Subtract(DateTime.UtcNow);
                        if (timeToWait > TimeSpan.Zero)
                        {
                            if (_checkStateHandle.WaitOne((int) Math.Min(timeToWait.TotalMilliseconds, int.MaxValue)))
                            {
                                if (Log.IsDebugEnabled)
                                    Log.Debug("Continuing because of a signal");
                            }
                            else if (Log.IsDebugEnabled)
                                Log.Debug("Continuing because of planned execution");
                        }
                        if (IsBeingStopped)
                            return;

                        if (DateTime.UtcNow >= _nextTaskExecution)
                        {
                            foreach (var task in TaskManager.GetExecutableTasks())
                            {
                                Exception exception = null;
                                try
                                {
                                    if (Log.IsDebugEnabled)
                                        Log.Debug("Executing task " + task);

                                    task.Execute();
                                }
                                catch (Exception ex)
                                {
                                    Log.Warn("Failed to execute task " + task, ex);
                                    exception = ex;
                                }
                                finally
                                {
                                    task.OnExecutionFinished(exception);
                                }
                            }
                        }
                        _nextTaskExecution = TaskManager.GetNextTaskExecution();
                    }
                }
            }
            finally
            {
                IsRunning = false;
                IsBeingStopped = false;
                Log.Info("Task execution agent stopped");
            }
        }
    }
}
